
#include <ctime>
#include <chrono>
#include <stdio.h>
#include <string.h>
#include <list>
#include <nice/agent.h>
#include <stun/debug.h>
#include <gio/gio.h>
#include <glib.h>
#include <netinet/in.h>
#include <unistd.h>

#include "agent.h"
#include "codecs.hpp"
#include "expire_date.hpp"
#include "xm_log.h"
#include "xm_crash.h"
#include "xm_version.h"
#include "http_parser.h"
#include "rtc_manager.h"
#include "xdtls_srtp.h"
#include "xplugin_rtc_debug.h"
#include "xm_app_config.h"
#include "NMedia/NLogger.hpp"

#include "client/linux/handler/exception_handler.h"

extern "C" {
void nice_enable_mux_socket(gboolean enabled);
}

#define conn_dbgv(FMT, ARGS...) dbgv("|%s| " FMT, conn->obj_id, ##ARGS )
#define conn_dbgi(FMT, ARGS...) dbgi("|%s| " FMT, conn->obj_id, ##ARGS )
#define conn_dbge(FMT, ARGS...) dbge("|%s| " FMT, conn->obj_id, ##ARGS )

typedef struct rtc_mserver_st * rtc_mserver_t;
typedef struct ctrlconn_st * ctrlconn_t;

#define CONN_BUF_SIZE (80*1024)
struct ctrlconn_st
{
    char obj_id[32];

    std::list<ctrlconn_t>::iterator ref;
    rtc_mserver_t		owner;
//    struct sockaddr_in  addr;
    char from_ip[INET6_ADDRSTRLEN];
    int from_port;

    GSocketConnection * connection;
    GInputStream *      istream;
    GOutputStream *     ostream;
    uint8_t				buf[CONN_BUF_SIZE];
    uint32_t			buf_pos;
    uint32_t			parse_pos;
    uint32_t			json_opens ;
    uint32_t			json_closes ;

    http_parser  *      parser;
    http_parser         parser_storage;
    http_parser_settings parser_settings;

    uint8_t				content[CONN_BUF_SIZE];
    uint32_t            content_pos;
    uint32_t            content_length;

    uint8_t *           send_data;
    uint32_t            send_length;
    uint32_t            sent_bytes;
    int                 disable_read;

    const char *        req_url;
    size_t                 req_url_length;
};


struct rtc_mserver_st{
    GMainLoop * mainloop;
    GSocketService * tcp_service;
    std::list<ctrlconn_t> * conn_list; // connection list
    rtc_manager_t rtcmgr;
};

#define ZERO_ALLOC(o, type, sz) do{o=(type)malloc(sz); memset(o, 0, sz);}while(0)

static
void on_conn_data_read (GObject * source_object,
                          GAsyncResult *res,
                          gpointer user_data);
static
void send_http_response(ctrlconn_t conn);

const char* config_url = "/config";



static
void ctrlconn_delete(ctrlconn_t conn){
    if(!conn) return;
    conn_dbgv("close http connection");
    conn->owner->conn_list->erase(conn->ref);
    if(conn->connection){
        g_object_unref (G_SOCKET_CONNECTION (conn->connection));
    }

    free(conn);
}


static 
bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
    dbgv("Dump path: %s", descriptor.path() );
    return succeeded;
}


static
int http_callback_on_url (http_parser* parser, const char *at, size_t length){
    ctrlconn_t conn = (ctrlconn_t) parser->data;
    conn_dbgv("http_callback_on_url: method=%s, url=%.*s", http_method_str((http_method)parser->method), (int)length, at);
    conn->req_url = at;
    conn->req_url_length = length;
    return 0;
}

static
int http_callback_on_header_field (http_parser* parser, const char *at, size_t length){
    ctrlconn_t conn = (ctrlconn_t) parser->data;
    conn_dbgv("http_callback_on_header_field: %.*s", (int)length, at);
    conn->parser = conn->parser; // for suppress warning
    return 0;
}

static
int http_callback_on_header_value (http_parser* parser, const char *at, size_t length){
    ctrlconn_t conn = (ctrlconn_t) parser->data;
    conn_dbgv("http_callback_on_header_value: %.*s", (int)length, at);
    conn->parser = conn->parser; // for suppress warning
    return 0;
}

static
void handle_config_get_request(ctrlconn_t conn);

static
int http_callback_on_headers_complete (http_parser* parser){
    ctrlconn_t conn = (ctrlconn_t) parser->data;
    conn_dbgv("http_callback_on_headers_complete: content_length=%lu", parser->content_length);
    if(parser->method == HTTP_GET){
        conn->disable_read = 1;
        if (conn->req_url_length >= strlen(config_url) && strncmp(conn->req_url, config_url, strlen(config_url)) == 0) {
            handle_config_get_request(conn);
        } else {
            conn_dbgi("Unsupport method GET: %.*s", (int)conn->req_url_length, conn->req_url);
            conn->content_pos = sprintf((char *) conn->content,
                                        "Unsupport GET method\r\n"
            );
            send_http_response(conn);
        }
    }
    conn->content_length = parser->content_length;

    return 0;
}

static
void on_conn_data_sent(GObject *source_object,
                       GAsyncResult *res,
                       gpointer user_data);

static
void handle_config_get_request(ctrlconn_t conn) {
    auto cfg = app_config_get();
    conn->content_pos = 0;
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<html>");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<body>");
    // enable mixer debug
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "enable-mixer-debug: ");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<form action=\"/config\" method=\"post\" style=\"display: inline;\" enctype=\"text/plain\">");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"hidden\" value=\"%s\" name=\"enable-mixer-debug\">",
                                  cfg->enable_mixer_debug ? "off" : "on");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"submit\" value=\"%s\">", cfg->enable_mixer_debug ? "on" : "off");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "</form>");
    // force mixer recording

    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<br/>force-mixer-recording: ");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<form action=\"/config\" method=\"post\" style=\"display: inline;\" enctype=\"text/plain\">");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"hidden\" value=\"%s\" name=\"force-mixer-recording\">",
                                  cfg->force_mixer_recording ? "off" : "on");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"submit\" value=\"%s\">", cfg->force_mixer_recording ? "on" : "off");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "</form>");

    // mixer-activate-level

    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<br/>mixer-activate-level: ");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<form action=\"/config\" method=\"post\" style=\"display: inline;\" enctype=\"text/plain\">");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"text\" value=\"%d\" name=\"mixer-activate-level\">",
                                  cfg->mixer_activate_level);
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"submit\" value=\"update\">");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "</form>");

    // opus lsb
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<br/>mixer-opus-lsb: ");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<form action=\"/config\" method=\"post\" style=\"display: inline;\" enctype=\"text/plain\">");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"text\" value=\"%d\" name=\"mixer-opus-lsb\">",
                                  cfg->mixer_opus_lsb);
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"submit\" value=\"update\">");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "</form>");

    // talker number
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<br/>mixer-talker-number (1-5): ");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<form action=\"/config\" method=\"post\" style=\"display: inline;\" enctype=\"text/plain\">");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"text\" value=\"%d\" name=\"mixer-talker-number\">",
                                  cfg->mixer_talker_number);
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "<input type=\"submit\" value=\"update\">");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "</form>");


    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "</body>");
    conn->content_pos += snprintf((char *) conn->content + conn->content_pos, sizeof(conn->content) - conn->content_pos,
                                  "</html>");

    conn->req_url = NULL;
    conn->req_url_length = 0;
    conn->send_length = sprintf((char *) conn->buf,
                                "HTTP/1.1 200 OK\r\n"
                                        "Access-Control-Allow-Origin: *\r\n"
                                        "Content-Type: text/html\r\n"
                                        "Content-Length: %d\r\n"
                                        "\r\n", conn->content_pos
    );

    conn->send_data = conn->buf;
    conn->sent_bytes = 0;

    conn_dbgv("response header (%d bytes):%.*s", conn->send_length, conn->send_length, conn->send_data);

    // send http header
    g_output_stream_write_async(conn->ostream,
                                conn->send_data + conn->sent_bytes,
                                conn->send_length - conn->sent_bytes,
                                G_PRIORITY_DEFAULT,
                                NULL, // GCancellable
                                on_conn_data_sent,
                                conn);
}

static
void handle_config_post_request(ctrlconn_t conn) {
    auto cfg = app_config_get();

    if (conn->content_length > 0) {
        std::string data((char*)conn->content, conn->content_length);
        // trim trailing \r\n
        data = data.substr(0, data.length()-2);
        auto sep = data.find("=");
        if (sep != std::string::npos) {
            std::string key = data.substr(0, sep);
            std::string value = data.substr(sep+1);

            if (key == "enable-mixer-debug") {
                if (value == "on") {
                    cfg->enable_mixer_debug = true;
                    dbgi("config: mixer debug: on");
                } else if (value == "off") {
                    cfg->enable_mixer_debug = false;
                    dbgi("config: mixer debug: off");
                }
            } else if (key == "force-mixer-recording") {
                if (value == "on") {
                    cfg->force_mixer_recording = true;
                    dbgi("config: mixer force recorder: on");
                } else if (value == "off") {
                    cfg->force_mixer_recording = false;
                    dbgi("config: mixer force recorder: off");
                }
            } else if (key == "mixer-activate-level") {
                int8_t d = atoi(value.c_str());
                if (d < -127) {
                    d = -127;
                } else if (d > 0) {
                    d = 0;
                }
                cfg->mixer_activate_level = d;
                dbgi("config: mixer activate level: %d", d);
            } else if (key == "mixer-opus-lsb") {
                int lsb = atoi(value.c_str());
                if (lsb < 8) {
                    lsb = 8;
                } else if (lsb > 16) {
                    lsb = 16;
                }
                cfg->mixer_opus_lsb = lsb;
                dbgi("config: mixer opus lsb: %d", lsb);
            } else if (key == "mixer-talker-number") {
                int nb = atoi(value.c_str());
                if (nb < 1) {
                    nb = 1;
                } else if (nb > 5) {
                    nb = 5;
                }
                cfg->mixer_talker_number = nb;
                dbgi("config: mixer talker number: %d", nb);
            }
        }
    }
    handle_config_get_request(conn);
}

static
int http_callback_on_body (http_parser* parser, const char *at, size_t length){
    ctrlconn_t conn = (ctrlconn_t) parser->data;
    conn_dbgv("http_callback_on_body (%lu bytes): %.*s\n", length, (int)length, at);

    if(parser->method != HTTP_POST){
        conn_dbge("unexpected body for post method");
        return -1;
    }


    memcpy(conn->content+conn->content_pos, at, length);
    conn->content_pos += length;
    if(conn->content_pos >= conn->content_length){
        conn->content[conn->content_pos] = '\0';
        conn_dbgv("content:%.*s", conn->content_pos, conn->content);
        conn->disable_read = 1;

        if (conn->req_url_length >= strlen(config_url) && strncmp(conn->req_url, config_url, strlen(config_url)) == 0) {
            handle_config_post_request(conn);
        } else {
            rtcmgr_handle_req(conn->owner->rtcmgr, conn->content, conn->content_pos, conn->content, &conn->content_pos, conn->from_ip, conn->from_port);
            send_http_response(conn);
        }
    }
    return 0;
}

static inline
uint64_t get_next_conn_id(){
    static uint64_t next_id = 0;
    return ++next_id;
}

static
ctrlconn_t ctrlconn_create(rtc_mserver_t owner, GSocketConnection * connection){
    ctrlconn_t conn = NULL;
    ZERO_ALLOC(conn, ctrlconn_t, sizeof(struct ctrlconn_st));
    snprintf(conn->obj_id, sizeof(conn->obj_id), "conn-%lu", get_next_conn_id());
    conn->owner = owner;
    conn->ref = owner->conn_list->insert(owner->conn_list->end(), conn);

    conn->connection = (GSocketConnection *)g_object_ref (connection);
    conn->istream = g_io_stream_get_input_stream  (G_IO_STREAM (connection));
    conn->ostream = g_io_stream_get_output_stream (G_IO_STREAM (connection));

    conn->parser = &conn->parser_storage;
    http_parser_init(conn->parser, HTTP_REQUEST);
    conn->parser->data = conn;
    memset(&conn->parser_settings, 0, sizeof(conn->parser_settings));
    conn->parser_settings.on_url = http_callback_on_url;
    conn->parser_settings.on_headers_complete = http_callback_on_headers_complete;
    conn->parser_settings.on_header_field = http_callback_on_header_field;
    conn->parser_settings.on_header_value = http_callback_on_header_value;
    conn->parser_settings.on_body = http_callback_on_body;

    GError * error = NULL;
    struct sockaddr native_addr;
    NiceAddress naddr;

    GSocketAddress * gaddr = g_socket_connection_get_remote_address(connection, &error);
    g_socket_address_to_native (gaddr, &native_addr, sizeof(native_addr), &error);
    nice_address_set_from_sockaddr (&naddr, &native_addr);
    nice_address_to_string (&naddr, conn->from_ip);
    conn->from_port = nice_address_get_port(&naddr);

    conn_dbgv("new connection from %s:%d", conn->from_ip, conn->from_port);
    g_object_unref(gaddr);

    g_input_stream_read_async (conn->istream,
                               conn->buf + conn->buf_pos,
                               CONN_BUF_SIZE-conn->buf_pos,
                               G_PRIORITY_DEFAULT,
                               NULL,
                               on_conn_data_read,
                               conn);

    return conn;
}

static
void on_conn_data_read (GObject * source_object,
                          GAsyncResult *res,
                          gpointer user_data){

    GError *error = NULL;
    ctrlconn_t conn = (ctrlconn_t)user_data;
    int count;
    GInputStream *istream = G_INPUT_STREAM (source_object);

    count = g_input_stream_read_finish (istream,
                                        res,
                                        &error);

    if(count == 0){
        conn_dbgv("read: connection close by peer");
        ctrlconn_delete(conn);
        return;
    }

    if(count < 0){
        conn_dbge("read: error=%s\n", error?error->message:"null");
        if (error != NULL) {
            g_clear_error (&error);
        }
        ctrlconn_delete(conn);
        return;
    }

    conn->buf_pos += count;
    conn->buf[conn->buf_pos] = '\0';
    conn_dbgv("<%lld> Message was: %d bytes, \"%s\"", (long long)pthread_self(), count, conn->buf);

    //size_t nparsed =
    http_parser_execute(conn->parser, &conn->parser_settings, (const char *)conn->buf, conn->buf_pos);
    if (conn->parser->upgrade) {
        conn_dbge("NOT support http upgrade");
        ctrlconn_delete(conn);
        return;
    } else if (conn->parser->http_errno) {
        conn_dbge("parse http fail, errno=%d (%s)", conn->parser->http_errno, http_errno_name((http_errno)conn->parser->http_errno));
        ctrlconn_delete(conn);
        return;
    }

    if(!conn->disable_read){
        conn->buf_pos = 0;
        g_input_stream_read_async (conn->istream,
                                   conn->buf + conn->buf_pos,
                                   CONN_BUF_SIZE-conn->buf_pos,
                                   G_PRIORITY_DEFAULT,
                                   NULL,
                                   on_conn_data_read,
                                   conn);
    }

}


static
void on_conn_data_sent (GObject * source_object,
                   GAsyncResult *res,
                   gpointer user_data){
    GOutputStream *stream = G_OUTPUT_STREAM (source_object);
    GError *error = NULL;
    ctrlconn_t conn = (ctrlconn_t)user_data;
    int count;

    count = g_output_stream_write_finish (stream,
                                          res,
                                          &error);
    if(count == 0){
        conn_dbgi("write: connection close gracefully\n");
        ctrlconn_delete(conn);
        return;
    }

    if(count < 0){
        conn_dbge("write: error=%s\n", error?error->message:"null");
        if (error != NULL) {
            g_clear_error (&error);
        }
        ctrlconn_delete(conn);
        return;
    }

    conn->sent_bytes += count;
    // send remains
    if(conn->sent_bytes < conn->send_length){
        g_output_stream_write_async(conn->ostream,
                                    conn->send_data+conn->sent_bytes,
                                    conn->send_length-conn->sent_bytes,
                                    G_PRIORITY_DEFAULT,
                                    NULL, // GCancellable
                                    on_conn_data_sent,
                                    conn);
        return;
    }

    // send content
    if(conn->content_pos > 0){
        conn->send_data = conn->content;
        conn->send_length = conn->content_pos;
        conn->sent_bytes = 0;
        conn_dbgv("response content (%d bytes):%.*s", conn->send_length, conn->send_length, conn->send_data);
        g_output_stream_write_async(conn->ostream,
                                    conn->send_data+conn->sent_bytes,
                                    conn->send_length-conn->sent_bytes,
                                    G_PRIORITY_DEFAULT,
                                    NULL, // GCancellable
                                    on_conn_data_sent,
                                    conn);
        conn->content_pos = 0;
    }else{
        conn_dbgv("response sent");
        http_parser_init(conn->parser, HTTP_REQUEST);
        conn->disable_read = 0;
        conn->buf_pos = 0;
        g_input_stream_read_async (conn->istream,
                                   conn->buf + conn->buf_pos,
                                   CONN_BUF_SIZE-conn->buf_pos,
                                   G_PRIORITY_DEFAULT,
                                   NULL,
                                   on_conn_data_read,
                                   conn);
    }

    // sent done

}

static
void send_http_response(ctrlconn_t conn){
    // Content-Type: text/plain
    // Content-Type: application/json

    conn->req_url = NULL;
    conn->req_url_length = 0;

    conn->send_length =  sprintf((char *)conn->buf,
                                 "HTTP/1.1 200 OK\r\n"
                                 "Access-Control-Allow-Origin: *\r\n"
                                 "Content-Type: application/json\r\n"
                                 "Content-Length: %d\r\n"
                                 "\r\n"
                                 , conn->content_pos
                                 );

    conn->send_data = conn->buf;
    conn->sent_bytes = 0;

    conn_dbgv("response header (%d bytes):%.*s", conn->send_length, conn->send_length, conn->send_data);

    // send http header
    g_output_stream_write_async(conn->ostream,
                                conn->send_data+conn->sent_bytes,
                                conn->send_length-conn->sent_bytes,
                                G_PRIORITY_DEFAULT,
                                NULL, // GCancellable
                                on_conn_data_sent,
                                conn);


}



static gboolean
incoming_callback (GSocketService *service,
                   GSocketConnection * connection,
                   GObject * source_object,
                   gpointer user_data)
{
    rtc_mserver_t obj = (rtc_mserver_t) user_data;

    ctrlconn_create(obj, connection);
    return FALSE;
}

static
gboolean timeout_callback (gpointer user_data){
    dbgi("timeout callback");
    rtc_mserver_t obj = (rtc_mserver_t) user_data;
    g_main_loop_quit(obj->mainloop);
    return TRUE;
}

void rtc_mserver_delete(rtc_mserver_t obj){
    if(!obj) return;
    g_socket_service_stop (obj->tcp_service);

    if(obj->conn_list){
        while(!obj->conn_list->empty()){
            ctrlconn_t conn = obj->conn_list->front();
            ctrlconn_delete(conn);
        }
        delete obj->conn_list;
        obj->conn_list = NULL;
    }

    free(obj);
}

rtc_mserver_t rtc_mserver_create(const char * listen_ip, int listen_port, rtc_manager_t rtcmgr){
    GError *error = NULL;
    int ret = 0;
    rtc_mserver_t obj = NULL;
    do{
        ZERO_ALLOC(obj, rtc_mserver_t, sizeof(struct rtc_mserver_st));
        obj->conn_list = new std::list<ctrlconn_t>;
        obj->tcp_service = g_socket_service_new ();
        g_socket_listener_set_backlog(G_SOCKET_LISTENER(obj->tcp_service), 1000);
        GInetAddress *address = g_inet_address_new_from_string(listen_ip);
        GSocketAddress *socket_address = g_inet_socket_address_new(address, listen_port);
        GSocketAddress *effective_address = NULL;
        ret = g_socket_listener_add_address(G_SOCKET_LISTENER(obj->tcp_service),
                socket_address, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP,
                NULL, &effective_address, &error);


        if (app_config_get()->http_port == 0) {
            app_config_get()->http_port = g_inet_socket_address_get_port(
                    (GInetSocketAddress*)effective_address);
            dbgw("http_port selected: %u",app_config_get()->http_port);
        }

        g_object_unref(socket_address);
        g_object_unref(address);


        //ret = g_socket_listener_add_inet_port (G_SOCKET_LISTENER (obj->tcp_service), listen_port, NULL, &error);
        if (!ret || error != NULL){
            dbge ("%s", error->message);
            g_clear_error (&error);
            ret = -1;
            break;
        }

        g_signal_connect (obj->tcp_service,
                          "incoming",
                          G_CALLBACK (incoming_callback),
                          obj);

        g_socket_service_start (obj->tcp_service);

//        //guint
//        g_timeout_add (5000, // milliseconds
//                       timeout_callback,
//                       obj);

        obj->mainloop = g_main_loop_new(NULL, FALSE);
        obj->rtcmgr = rtcmgr;
        ret = 0;

    }while(0);
    if(ret){
        rtc_mserver_delete(obj);
        obj = NULL;
    }
    return obj;
}

void rtc_mserver_loop(rtc_mserver_t obj){
    g_main_loop_run(obj->mainloop);
}

#include "xtlv_file.h"
#include "xcutil.h"
//#include "webrtc/modules/audio_coding/include/audio_coding_module.h"
//#include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h"
#include "audio_recveq.h"
//using namespace webrtc;
int test_main (int argc, char **argv){

    int ret = -1;

    audio_recveq_t recveq = audio_recveq_create();
    audio_recveq_reg_codec(recveq, "opus", 111);



    const char * filename = "/Users/simon/Desktop/simon/projects/easemob/src/rtc/rtc-xswitch/bin/rtc-xswitch.rtc.20170106-202900.rtc-1.tlv";
    FILE * fpin = NULL;
    unsigned int buf_size = 1*1024*1024;
    unsigned char * buf = (unsigned char *)malloc(buf_size);

    FILE * fpout = NULL;
    const char * filename_out = "out.pcm";
    fpout = fopen(filename_out, "wb");
    unsigned num_packets = 0;

    do{
        fpin = fopen(filename, "rb");
        if(!fpin){
            gdbge("fail to open input file %s\n", filename);
            ret = -1;
            break;
        }
        gdbgi("successfully reading open %s", filename);

        int type;
        int raw_length = 0;

        int64_t time_start = -1;
        int64_t start_pts = -1;
        // int64_t last_pts = 0;
        int64_t next_play_time = get_timestamp_ms();
        int64_t pts = 0;
        int out_bytes = 0;
        while(1){


            if(raw_length == 0){
                type = -1;
                while(type != RTC_AUDIO){
                    ret = tlv_file_read_raw(fpin, buf, buf_size, &raw_length, &type);
                    if(ret < 0){
                        dbge("reach file end, ret=%d", ret);
                        ret = -1;
                        break;
                    }

                    if(raw_length < 8){
                        dbge("udp data length too short, %d", raw_length);
                        ret = -1;
                        break;
                    }
                    pts = 0;
                    unsigned int t32;
                    t32 = le_get_u32(buf+0);
                    pts |= t32;
                    t32 = le_get_u32(buf+4);
                    pts |= ((int64_t)t32) << 32;
                }
                if(ret<0) break;

            }

            int64_t now = get_timestamp_ms();
            if(start_pts < 0){
                start_pts = pts;
                time_start = now;
                // last_pts = pts;
            }


            int64_t read_sleep_ms = 0;
            int64_t pts_elapsed = 0;
            int64_t time_elapsed = now - time_start;
            if(pts > start_pts ){
                pts_elapsed = pts - start_pts;
                if(pts_elapsed > time_elapsed){
                    read_sleep_ms = (pts_elapsed-time_elapsed);
                }
            }

            int64_t play_sleep_ms = next_play_time - now;

            // gdbgi("--------- play_sleep_ms=%lld, read_sleep_ms=%lld",  play_sleep_ms, read_sleep_ms);


            if(now >= next_play_time){
                uint32_t timestamp;
                int16_t * sample_data;
                int samples_per_channel;
                int num_channels;
                ret = audio_recveq_pull_pcm(recveq, &timestamp, &sample_data, &samples_per_channel, &num_channels);
                gdbgi("pcm: ts=%u, samples_per_channel=%d, num_channels=%d", timestamp, samples_per_channel, num_channels);
                if(ret == 0){
                    fwrite(sample_data, 1, samples_per_channel * num_channels * sizeof(int16_t), fpout);
                    out_bytes += (samples_per_channel * num_channels * sizeof(int16_t));
                    // gdbgi("out: timestamp=%u, out_bytes = %d", timestamp, out_bytes);
                }
                next_play_time = next_play_time + 10;
                play_sleep_ms = next_play_time - now;
            }

            int64_t sleep_ms = play_sleep_ms < read_sleep_ms ? play_sleep_ms : read_sleep_ms;
            if(sleep_ms > 0){
                // gdbgi("sleep %d ms", (int)sleep_ms);
                usleep(sleep_ms*1000);
            }

            now = get_timestamp_ms();
            time_elapsed = now - time_start;
            // gdbgi("time_elapsed_diff=%lld",  time_elapsed-pts_elapsed);
            if(time_elapsed >= pts_elapsed && raw_length > 0){
                int length = raw_length;
                raw_length = 0;

                if(type != RTC_AUDIO){
                    continue;
                }

                unsigned char * data = buf + 8;
                int datalength = length - 8;

                // audio_recveq_push_rtp(recveq, data, datalength);

                num_packets++;
                if(num_packets > 300){
                    gdbgi("reach max packets");
                    break;
                }
                if(num_packets >= 100 && num_packets < 250){
                    gdbgi("drop packet");

                }else{
                    audio_recveq_push_rtp(recveq, data, datalength);
                }



                // gdbgi("pts diff %lld ms", pts-last_pts);
                // last_pts = pts;

            }


        }
        if(ret < 0) break;

        ret = 0;
    }while(0);

    if(fpin){
        fclose(fpin);
        fpin = NULL;
    }

    if(fpout){
        fclose(fpout);
        fpout = NULL;
    }

    if(buf){
        free(buf);
        buf = NULL;
    }

    if(recveq){
        audio_recveq_delete(recveq);
        recveq = NULL;
    }

    exit(0);
    return 0;
}



// 3 header for inet_aton
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int check_localip(const char *localip){
//    localip = "172.17.1.197";
    struct sockaddr_in sin = { 0 };
    int sock = -1;
    int ret = -1;

    do{
        sin.sin_family = AF_INET;
        sin.sin_port = htons(0);
        inet_aton(localip, &sin.sin_addr);
        sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if(sock < 0){
            ret = -111;
            break;
        }

        ret = bind(sock, (struct sockaddr *) &sin, sizeof(sin));
        if(ret < 0){
            ret = -222;
            break;
        }

        ret = 0;
    }while(0);

    if(sock >= 0){
        close(sock);
    }

    return ret;
}

static
void dump_config(app_config * config){
    gdbgi("=============OPTIONS=============");
    gdbgi("        log-config=[%s]", config->log_config_file_name);
    gdbgi("          media-ip=[%s]", config->media_ip);
    gdbgi("         media-ip2=[%s]", config->media_ip2);
    gdbgi("          min-port=[%d]", config->min_port);
    gdbgi("          max-port=[%d]", config->max_port);
    gdbgi("           http-ip=[%s]", config->http_ip);
    gdbgi("         http-port=[%d]", config->http_port);
    gdbgi("  http-external-ip=[%s]", config->http_external_ip);
    gdbgi("http-external-port=[%d]", config->http_external_port);
    gdbgi("          tlv-path=[%s]", config->tlv_path);
    gdbgi("          job-path=[%s]", config->job_path);
    gdbgi("            fbpass=[%s]", config->feedback_passthrough ? "yes" : "no");
    gdbgi("             debug=[%s]", config->debug_mode_string);
    gdbgi("          dump-rtp=[%s]", config->is_dumping_rtp ? "yes" : "no");
    gdbgi("         dump-rtcp=[%s]", config->is_dumping_rtp ? "yes" : "no");
    gdbgi("max-video-br(kbps)=[%d]", config->max_video_bitrate_kbps);
    gdbgi("min-video-br(kbps)=[%d]", config->min_video_bitrate_kbps);
    gdbgi("        decode-fec=[%s]", config->is_translating_fec ? "yes" : "no");
    gdbgi("      disable-h264=[%s]", config->disable_h264 ? "yes" : "no");
    gdbgi("        enable-fec=[%s]", config->enable_fec ? "yes" : "no");
    gdbgi("   compensate-rtcp=[%s]", config->compensate_rtcp ? "yes" : "no");
    gdbgi("   experiment-esmb=[%s]", config->experiment_esmb ? "yes" : "no");
    gdbgi("        enable-bwe=[%s]", config->enable_bwe ? "yes" : "no");
    gdbgi("enable-mixer-debug=[%s]", config->enable_mixer_debug ? "yes" : "no");
    gdbgi("force-mixer-record=[%s]", config->force_mixer_recording ? "yes" : "no");
    gdbgi("    mixer-activate=[%d]", config->mixer_activate_level);
    gdbgi("    mixer-opus-lsb=[%d]", config->mixer_opus_lsb);
    gdbgi("  mixer-talker-num=[%d]", config->mixer_talker_number);
    gdbgi("          nice-mux=[%s]", config->nice_mux ? "yes" : "no");
    gdbgi("         machineId=[%d]", config->machineId);
    gdbgi("     crash-log-dir=[%s]", config->crash_log_dir);
    gdbgi("=================================");
}

#include <sys/stat.h>
static inline
int mk_dir_tree(const char * path){
    struct stat st = {0};

    if (stat(path, &st) == -1) {
        return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    }
    return 0;
}

void check_expiration() {
    if (expire_date == 0) {
        return;
    }
    gdbgi("this copy will expire at %d", expire_date);
    auto now = std::chrono::system_clock::now();
    std::tm expire = {0,};
    expire.tm_year = expire_date / 10000 - 1900;
    expire.tm_mon = (expire_date % 10000) / 100 - 1;
    expire.tm_mday = expire_date % 100;
    auto expire_time = mktime(&expire);
    auto expire_tp = std::chrono::system_clock::from_time_t(expire_time);
    if (now > expire_tp) {
        gdbgi("this copy has expired");
        exit(0);
    }
}

extern "C" {
#include <libavformat/avformat.h>
}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <net/if.h> /* for IFF_LOOPBACK */
#include <vector>
#include <string>

std::vector<std::string> getIPList(bool loopback = false, bool ipv4 = true, bool ipv6 = false){
    std::vector<std::string> ipList;
    struct ifaddrs * ifAddrStruct = NULL;
    getifaddrs(&ifAddrStruct);
    
    for (struct ifaddrs * ifa = ifAddrStruct; ifa!=NULL; ifa = ifa->ifa_next) {
        int sa_family = ifa->ifa_addr->sa_family;
        if(!(ifa->ifa_flags & IFF_UP)){
            continue;
        }
        if(sa_family != AF_INET && sa_family != AF_INET6){
            continue;
        }
        if(!loopback && (ifa->ifa_flags & IFF_LOOPBACK)){
            continue;
        }
        if(!ipv4 && sa_family == AF_INET){
            continue;
        }
        if(!ipv6 && sa_family == AF_INET6){
            continue;
        }
        
        void * tmpPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
        char addrBuf[INET6_ADDRSTRLEN];
        inet_ntop(ifa->ifa_addr->sa_family, tmpPtr, addrBuf, INET6_ADDRSTRLEN);
        std::string str = addrBuf;
        ipList.push_back(str);
        printf("[%s]: ip=[%s], flag=[%08X]\n", ifa->ifa_name, addrBuf, ifa->ifa_flags);
    } // for
    
    if (ifAddrStruct!=NULL){
        freeifaddrs(ifAddrStruct);
        ifAddrStruct = NULL;
    }
    
    return ipList;
}

static
std::string getFilePath(const std::string& relativePath){
    static std::string path;
    static char div = '\0';
    if(!div){
        const char * srcpath = __FILE__;
        div = '/';
        const char * p = strrchr(srcpath, div);
        if(!p){
            div = '\\';
            p = strrchr(__FILE__, div);
        }
        if(p){
            path = srcpath;
            path = path.substr(0, p-srcpath) + div;
        }
    }
    return path + relativePath;
}


static void fault(unsigned after) {
    std::this_thread::sleep_for(std::chrono::seconds{after});
    delete reinterpret_cast<std::string*>(0xFEE1DEAD);
}

int main (int argc, char **argv){

#ifndef NDEBUG
    
    printf("DEBUG verion\n");
//    spdlog::set_pattern("|%H:%M:%S.%e|%n|%L| %v");
//    spdlog::set_level(spdlog::level::debug);
//    static auto main_logger = NLogger::Get("main");
//    main_logger->info("hello num {}", 121);
    
    std::vector<std::string> iplist = getIPList();
    std::string filepath = getFilePath("../../log4cplus.properties");
    char * debug_argv[] = {argv[0]
        , (char *) "-c", (char *) filepath.c_str()
        , (char *) "--http-ip", (char*)"0"
        , (char *) "--http-port", (char*)"3019"
        , (char*)"--proxy-addr", (char*)"127.0.0.1:2021"
        , (char*)"--record-path", (char*)"/tmp/recorder"
        //, (char*)"--lost-0"
        , (char*)"--enable-fec"
    };
    if(argc ==1 && iplist.size() > 0){
        printf("auto select ip [%s]\n", iplist[0].c_str());
        debug_argv[4] = (char *)iplist[0].c_str();
        argv = debug_argv;
        argc = sizeof(debug_argv)/sizeof(debug_argv[0]);
    }
#else
    printf("RELEASE verion\n");
#endif

    //g_log_set_handler(nullptr, G_LOG_LEVEL_INFO, g_log_default_handler, NULL);
    
    
//#if MIXER_DEBUG
    av_register_all();
    avcodec_register_all();
//#endif
    // nice debug cost lots of cpu
    nice_debug_disable(TRUE);
    //stun_debug_enable();

    xm_crash_init(argc, argv);

    if(app_config_parse(argc, argv)){
        return 1;
    }

    app_config * config = app_config_get();
    xm_log_init(config->log_config_file_name);

    google_breakpad::MinidumpDescriptor descriptor(config->crash_log_dir);
    google_breakpad::ExceptionHandler eh(descriptor, NULL, dumpCallback, NULL, true, -1);

    // fault(1U);

    nice_enable_mux_socket(config->nice_mux);

    gdbgi("APP_VER = %s-%s", APP_VER_STR, GIT_DESCRIBE_STR);
    //gdbgi("GIT_VER = %s", GIT_DESCRIBE_STR);

    int  status = mk_dir_tree("/tmp/aaa");
    gdbgi("status = %d", status);

    dump_config(config);



    check_expiration();
//    test_main(argc, argv);

    if(config->media_ip && config->media_ip[0] != '\0'){
        if(check_localip(config->media_ip)){
            gdbge("fail to discovery media_ip %s", config->media_ip);
            return 1;
        }
        gdbgi("successfully check media_ip %s", config->media_ip);
    }


    const char * cert_file = NULL; // "/Users/simon/Desktop/simon/projects/easemob/src/xmedia/dtls/certs/server-cert.pem";
    const char * key_file = NULL; // "/Users/simon/Desktop/simon/projects/easemob/src/xmedia/dtls/certs/server-key.pem";
    int ret = xdtls_srtp_init(cert_file, key_file);
    if(ret < 0){
        gdbge("xdtls_srtp_init fail with %d", ret);
        return 1;
    }

    rtc_manager_t rtcmgr = rtcmgr_create();

    ret = xplugin_rtc_debug_init(argc, argv);
    if(ret < 0){
        gdbge("xplugin_rtc_debug_init fail with %d", ret);
        return 1;
    }

    rtc_codec_init(config->enable_fec, config->enable_bwe);

    gdbgi("http service at [%s:%d]", config->http_ip, config->http_port);
    rtc_mserver_t server = rtc_mserver_create(config->http_ip, config->http_port, rtcmgr);
    if(!server){
        return 2;
    }

    rtc_mserver_loop(server);

    rtc_mserver_delete(server);

    rtc_codec_uninit();

    xplugin_rtc_debug_exit();

    if(rtcmgr){
        rtcmgr_delete(rtcmgr);
        rtcmgr = NULL;
    }

    return 0;
}
